﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartMathLibrary.InternalRoutines;

namespace SmartMathLibrary.Optimization
{
    /// <summary>
    /// This class represents an multidimensional optimizer, which use the principal axis method.
    /// </summary>
    [Serializable]
    public class PrincipalAxisMultidimensionalOptimizer : AbstractMultidimensionalOptimizer
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PrincipalAxisMultidimensionalOptimizer"/> class.
        /// </summary>
        /// <param name="function">The base function to optimize.</param>
        public PrincipalAxisMultidimensionalOptimizer(IHardMultivariateRealFunction function)
            : base(function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PrincipalAxisMultidimensionalOptimizer"/> class.
        /// </summary>
        /// <param name="function">The base function to optimize.</param>
        public PrincipalAxisMultidimensionalOptimizer(HardMultivariateRealFunction function)
            : base(function)
        {
        }

        /// <summary>
        /// This method represents a wrapper function. 
        /// </summary>
        /// <param name="x">The vector components.</param>
        /// <param name="n">The number of vector components.</param>
        /// <returns>The result of the function at the position x.</returns>
        private unsafe double WrapperFunction(double* x, int* n)
        {
            GeneralVector tempuri = new GeneralVector(*n);

            for (int i = 0; i < *n; i++)
            {
                tempuri[i] = x[i];
            }

            return this.Function.SolveAt(tempuri);
        }

        /// <summary>
        /// Finds the minimum of the specified function by using the principal axis method.
        /// </summary>
        /// <param name="precision">The precision of the result.</param>
        /// <param name="stepSize">The maximum step size of the algorithm.</param>
        /// <param name="startValues">The start vector of the finding process.</param>
        /// <returns>The minimum of the function.</returns>
        public GeneralVector FindMinimum(double precision, double stepSize, GeneralVector startValues)
        {
            double machinePrecision = 1e-8;

            return this.InternalFindMinimum(ref precision, ref machinePrecision, ref stepSize, startValues);
        }

        /// <summary>
        /// Internal algorithm for finding the minimum.
        /// </summary>
        /// <param name="tolerance">The tolerance of the result.</param>
        /// <param name="machinePrecision">The machine precision.</param>
        /// <param name="stepSize">The maximum step size of the algorithm.</param>
        /// <param name="startValues">The start vector of the finding process.</param>
        /// <returns>The minimum of the function.</returns>
        private unsafe GeneralVector InternalFindMinimum(ref double tolerance, ref double machinePrecision,
                                                         ref double stepSize, GeneralVector startValues)
        {
            int prin = 0;
            int n = startValues.Count;
            double[] tempuri = (double[]) startValues.VectorData.Clone();
            GeneralVector result = new GeneralVector(startValues.Count);

            fixed (double* pTolerance = &tolerance)
            {
                fixed (double* pMachinePrecision = &machinePrecision)
                {
                    fixed (double* pStepSize = &stepSize)
                    {
                        fixed (double* pStartValues = tempuri)
                        {
                            PrincipalAxisHelperFunction a = WrapperFunction;
                            PrincipalAxisOptimizerProvider.PrincipalAxisMethod(pTolerance, pMachinePrecision, pStepSize,
                                                                               &n, &prin, pStartValues, a, null);

                            for (int i = 0; i < startValues.Count; i++)
                            {
                                result[i] = pStartValues[i];
                            }
                        }
                    }
                }
            }

            return result;
        }
    }
}